// Gmsh - Copyright (C) 1997-2019 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
//
// Contributed by Matti Pellikka <matti.pellikka@gmail.com>.

#ifndef CELL_H
#define CELL_H

#include <map>
#include <vector>
#include "MElement.h"

class Cell;

class Less_Cell {
public:
    bool operator()(const Cell *c1, const Cell *c2) const;
};

// Class to save cell boundary orientation information
class BdInfo {
private:
    signed char _ori[2];

public:
    BdInfo(int ori)
    {
        _ori[0] = ori;
        _ori[1] = 0;
    }

    int get() const { return _ori[0]; }
    void reset() { _ori[0] = _ori[1]; }
    void init() { _ori[1] = _ori[0]; }
    void set(int ori) { _ori[0] = ori; }
    int geto() const { return _ori[1]; }
};

// Class representing an elementary cell of a cell complex.
class Cell {
protected:
    static int _globalNum;

    int _num;
    char _domain;

    // whether this cell a combinded cell of elementary cells
    bool _combined;
    // for some algorithms to omit this cell
    bool _immune;

    // list of cells on the boundary and on the coboundary of this cell
    std::map<Cell *, BdInfo, Less_Cell> _bd;
    std::map<Cell *, BdInfo, Less_Cell> _cbd;

    Cell() {}

private:
    char _dim;
    std::vector<MVertex *> _v;
    // sorted vertices of this cell (used for ordering of the cells)
    std::vector<char> _si;

    inline bool _sortVertexIndices();

public:
    static std::pair<Cell *, bool> createCell(MElement *element, int domain);
    static std::pair<Cell *, bool> createCell(Cell *parent, int i);

    Cell(MElement *element, int domain);
    Cell(Cell *parent, int i);

    virtual ~Cell() {}

    int getDomain() const { return _domain; }
    void setDomain(int domain) { _domain = domain; }
    int getNum() const { return _num; }
    void setNum(int num) { _num = num; };
    int getTypeMSH() const;
    virtual int getDim() const { return _dim; }
    bool inSubdomain() const { return _domain ? true : false; }
    void getMeshVertices(std::vector<MVertex *> &v) const { v = _v; }

    void setImmune(bool immune) { _immune = immune; };
    bool getImmune() const { return _immune; };

    int getNumSortedVertices() const { return _si.size(); }
    inline int getSortedVertex(int vertex) const;
    int getNumVertices() const { return _v.size(); }
    MVertex *getMeshVertex(int vertex) const { return _v.at(vertex); }

    void findBdElement(int i, std::vector<MVertex *> &vertices) const;
    int getNumBdElements() const;
    int findBdCellOrientation(Cell *cell, int i) const;

    void increaseGlobalNum() { _globalNum++; }

    // save/restore the original boundary information of the cell
    void saveCellBoundary();
    void restoreCellBoundary();

    // true if this cell has given vertex
    virtual bool hasVertex(int vertex) const;

    // (co)boundary cell iterator
    typedef std::map<Cell *, BdInfo, Less_Cell>::iterator biter;

    // iterators to (first/last (co)boundary cells of this cell
    // (orig: to original (co)boundary cells of this cell)
    biter firstBoundary(bool orig = false);
    biter lastBoundary();
    biter firstCoboundary(bool orig = false);
    biter lastCoboundary();

    int getBoundarySize(bool orig = false);
    int getCoboundarySize(bool orig = false);

    // get the (orig: original) cell boundary
    void getBoundary(std::map<Cell *, short int, Less_Cell> &boundary,
                     bool orig = false);
    void getCoboundary(std::map<Cell *, short int, Less_Cell> &coboundary,
                       bool orig = false);

    // add (co)boundary cell
    // (other: reciprocally also add this cell from the other cell's (co)boundary)
    void addBoundaryCell(int orientation, Cell *cell, bool other);
    void addCoboundaryCell(int orientation, Cell *cell, bool other);

    // remove (co)boundary cell
    // (other: reciprocally also revove this cell from the other cell's
    // (co)boundary)
    void removeBoundaryCell(Cell *cell, bool other);
    void removeCoboundaryCell(Cell *cell, bool other);

    // true if has given cell on (orig: original) (co)boundary
    bool hasBoundary(Cell *cell, bool orig = false);
    bool hasCoboundary(Cell *cell, bool orig = false);

    // print cell debug info
    virtual void printCell();
    virtual void printBoundary();
    virtual void printCoboundary();

    // tools for combined cells
    bool isCombined() const { return _combined; }

    typedef std::map<Cell *, int, Less_Cell>::iterator citer;
    virtual void getCells(std::map<Cell *, int, Less_Cell> &cells)
    {
        cells.clear();
        cells[this] = 1;
    }
    virtual int getNumCells() const { return 1; }

    bool operator==(const Cell &c2) const
    {
        return (this->getNum() == c2.getNum());
    }
};

// A cell that is a combination of cells of same dimension
class CombinedCell : public Cell {
private:
    // list of cells this cell is a combination of
    std::map<Cell *, int, Less_Cell> _cells;

public:
    CombinedCell(Cell *c1, Cell *c2, bool orMatch, bool co = false);
    CombinedCell(std::vector<Cell *> &cells);
    ~CombinedCell() {}

    int getDim() const { return _cells.begin()->first->getDim(); }
    void getCells(std::map<Cell *, int, Less_Cell> &cells) { cells = _cells; }
    int getNumCells() const { return _cells.size(); }
    bool hasVertex(int vertex) const;

    bool operator==(const Cell &c2) const
    {
        return (this->getNum() == c2.getNum());
    }
};

#endif
